/**
 *
 * Copyright  2011 Nokia Corporation.
 * tuomo.hirvonen@digia.com
 *
 */



#include <stdio.h>
#include <stdlib.h>
#include "thelevel.h"
#include "thegame.h"

float number_colors[] = {
    0,0,0,0,                    // zero.. not used
    0.2f,0.2f,0.6f,1.0f,        // 1
    0.2f,0.6f,0.2f,1.0f,        // 2
    0.6f,0.2f,0.2f,1.0f,        // 3

    0.4f,0.4f,0.8f,1.0f,        // 1
    0.4f,0.8f,0.4f,1.0f,        // 2
    0.8f,0.4f,0.4f,1.0f,        // 3

    1.0f,1.0f,0.0f,1.0f,        // 7
    1.0f,0.0f,0.0f,1.0f
};        // 8


TheLevel::TheLevel( TheGame *g, int w, int h, int mp ) {
    game = g;
    width = w;
    height = h;
    levelData = new unsigned char[w*h];
    levelTiles = new unsigned char[w*h];
    for (int f=0; f<w*h; f++) levelData[f] = 0;

    memset( levelTiles, 0, w*h );
    mineMarkAnimationState = new float[w*h];

    showPreviewMap = false;
    zoomAmount = 0.0f;

    openCount = 0;
    flash = false;
    flashCount = 0.0f;
    minePercentage = mp;
    resetLevel( minePercentage, -100,-100);


};


TheLevel::~TheLevel() {
    delete [] levelData;
    delete [] levelTiles;
    delete [] mineMarkAnimationState;
}


unsigned char TheLevel::getLevelDataAt( int x, int y ) {
    if (x>=0 && y>=0 && x<width && y<height)
        return levelData[ y*width + x];
    else
        return 0;
}

void TheLevel::reCalculateTileIndices() {

    unsigned char localMap[3][3];

    int ul, ur, dl, dr,ce;

    int lmx,lmy;
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {

            for (lmy=-1; lmy<=1; lmy++)
                for (lmx=-1; lmx<=1; lmx++) {
                    localMap[lmy+1][lmx+1] = (getLevelDataAt( x+lmx, y+lmy )&1);
                    if (localMap[lmy+1][lmx+1]==0)
                        localMap[lmy+1][lmx+1] = 1;
                    else
                        localMap[lmy+1][lmx+1] = 0;
                }




            ce = localMap[1][1];
            ul = localMap[1][0] | localMap[0][0] | localMap[0][1] | localMap[1][1];
            ur = localMap[0][2] | localMap[0][1] | localMap[1][2] | localMap[1][1];
            dl = localMap[1][0] | localMap[2][0] | localMap[2][1] | localMap[1][1];
            dr = localMap[2][1] | localMap[2][2] | localMap[1][2] | localMap[1][1];
            levelTiles[y*width+x] = dl | (dr<<1) | (ur<<2) | (ul<<3) | (ce<<4);
        }
    }
}

void TheLevel::resetLevel(int minePercentage, int avoidx, int avoidy) {
    levelCompleted = false;
    gameOver = false;
    int mineCount = width*height * minePercentage / 100;
    totalMines = mineCount;
    int i = 0;
    for (int y=0; y<height; y++)
        for (int x=0; x<width; x++) {
            levelData[i] = 0;           // not cleared
            mineMarkAnimationState[i] = 0.0f;
            i++;
        }

    while (mineCount>0) {
        int xpos = (((rand() & 511) * width) >> 9 );
        int ypos = (((rand() & 511) * height) >> 9 );

        int dis = abs(xpos-avoidx) + abs(ypos-avoidy);

        if ((levelData[ypos*width+xpos] & 2) == 0 && dis>2)
        {
            levelData[ypos*width+xpos] |= 2;
            mineCount--;
        }
    }


    // calculate numbers into the levelData
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            unsigned char minesAround =
                    ((getLevelDataAt(x-1,y-1)>>1)&1)+
                    ((getLevelDataAt(x,y-1)>>1)&1)+
                    ((getLevelDataAt(x+1,y-1)>>1)&1)+
                    ((getLevelDataAt(x-1,y)>>1)&1)+
                    ((getLevelDataAt(x+1,y)>>1)&1)+
                    ((getLevelDataAt(x-1,y+1)>>1)&1)+
                    ((getLevelDataAt(x,y+1)>>1)&1)+
                    ((getLevelDataAt(x+1,y+1)>>1)&1);

            // place into the 4 most significat
            levelData[ y*width +x ] |= (minesAround<<4);
        }
    }

    reCalculateTileIndices();
}

void TheLevel::openTile( int x, int y) {
    if (x<0 || y<0 || x>=width || y>=height) return;
    if ((levelData[y*width+x]&4)!=0) return;     // dont open a tile with a mine flag.

    if (openCount==0) {
        // now its time to reset the level when we know the first clickposition
        resetLevel(minePercentage, x, y);
    }

         // already opened or marked as mine.
    if (((levelData[y*width+x]&1)!=0) ||
        ((levelData[y*width+x]&4)!=0)) return;

    openCount++;

        // Has a mine!
    if ((levelData[y*width+x]&2)!=0) {
        gameOver = true;
        // Create particle-explosion
        game->getParticleSystem()->spray(1, x*BLOCK_SIZE, y*BLOCK_SIZE, 0.0f, 0.0f, 0.0f, 0.0f, game->getExplosionSpray() );
        game->getParticleSystem()->spray(60, x*BLOCK_SIZE, y*BLOCK_SIZE, BLOCK_SIZE, 0.0f, 0.0f, BLOCK_SIZE*15.0f, game->getSparkleSpray() );

        return;
    } else {
        game->getParticleSystem()->spray(3, x*BLOCK_SIZE, y*BLOCK_SIZE, 30.0f, 0.0f, 0.0f, 300.0f, game->getStarSpray() );

    }

    levelData[ y * width + x] |= 1;              // raise the first bit
    levelData[ y * width + x] &= (255^4);              // erase the flagbit


        // open neighbours if we have zero mines as neighbours
    if ((levelData[y*width+x]>>4)==0) {
        openTile(x-1,y-1);
        openTile(x,y-1);
        openTile(x+1,y-1);

        openTile(x-1,y);
        openTile(x+1,y);

        openTile(x-1,y+1);
        openTile(x,y+1);
        openTile(x+1,y+1);

    }
}

bool TheLevel::click( float x, float y ) {
    if (gameOver || levelCompleted) return false;
    if (showPreviewMap) {
            // zoom into the clickposition?

        showPreviewMap = false;
        return true;
    }


    int blockx = (x+BLOCK_SIZE/2.0f)/BLOCK_SIZE;
    int blocky = (y+BLOCK_SIZE/2.0f)/BLOCK_SIZE;
    if (blockx<0 || blocky<0 || blockx>=width || blocky>=height) return false;

    openTile( blockx, blocky);
    reCalculateTileIndices();
    return true;
}

bool TheLevel::longClick( float x, float y ) {
    if (gameOver || levelCompleted || showPreviewMap) {
        return true;
    }
    int blockx = (x+BLOCK_SIZE/2.0f)/BLOCK_SIZE;
    int blocky = (y+BLOCK_SIZE/2.0f)/BLOCK_SIZE;
    if (blockx<0 || blocky<0 || blockx>=width || blocky>=height) return false;

    levelData[blocky*width+blockx] ^= 4;            // flip the 3:th bit

    return true;
}


void TheLevel::update( const float frameDelta ) {

    float t;
    int correctMarks = 0;
    mineMarks = 0;
    int closedTiles = 0;
    int calculatedMines = 0;
    for (int i=0; i<width*height; i++) {
        if ((levelData[i]&2) != 0) calculatedMines ++;
        if ((levelData[i]&4) || gameOver) t = 1.0f; else t = 0.0f;
        if ((levelData[i]&1)==0) closedTiles++;
        mineMarkAnimationState[i] += (t-mineMarkAnimationState[i]) * frameDelta*4.0f;
        if ((levelData[i]&4)!=0 && (levelData[i]&1)==0) {
            mineMarks++;
            if ((levelData[i]&2) != 0) correctMarks++;
        }
    }

        // Just a calculation test.
    totalMines = calculatedMines;

    if (correctMarks == totalMines && closedTiles == totalMines) {
        levelCompleted = true;
    }

    flashCount+=frameDelta;
    if (flashCount>0.5f) {
        flash = !flash;
        flashCount-=0.5f;
    }

    float previewZoomTarget = 0.0f;
    if (showPreviewMap) previewZoomTarget = 1.0f;
    zoomAmount += (previewZoomTarget - zoomAmount)*frameDelta*10.0f;
}


void TheLevel::render( SpriteBatch *batch,  float posx, float posy )
{
    SpriteDrawInfo sdi;

    float previewZoomAmount = (float)(batch->getTargetWidth()) / (width * BLOCK_SIZE);
    float zoomyofs = (float)(batch->getTargetHeight() - batch->getTargetWidth())/2.0f;

    float blockSize = BLOCK_SIZE * (1.0f-zoomAmount) + (float)(BLOCK_SIZE)*previewZoomAmount * zoomAmount;
    posx = posx*(1.0f-zoomAmount) + zoomAmount*BLOCK_SIZE*previewZoomAmount/2.0f;
    posy = posy*(1.0f-zoomAmount) + zoomAmount*BLOCK_SIZE*previewZoomAmount/2.0f + zoomAmount * zoomyofs;


    sdi.textureHandle = game->getGameTextures().levelTexture;
    sdi.setScale( blockSize, blockSize );
    int tilx,tily;


        // render bg
    for (int y=0; y<height; y++) {
        float blocky = posy + y*blockSize;

        if (blocky>=-blockSize && blocky<batch->getTargetHeight()+blockSize/2);
        for (int x=0; x<width; x++) {
            int lind = y*width+x;
            if (levelTiles[lind]!=255)
            {
                float blockx = posx + x*blockSize;
                if (blockx>=-BLOCK_SIZE && blockx<batch->getTargetWidth()+blockSize/2) {
                    tily = levelTiles[lind]/4;
                    tilx = levelTiles[lind] - tily*4;
                    sdi.setSourceRect( tilx/4.0f, tily/8.0f, 1.0f/4.0f, 1.0f/8.0f);

                    sdi.setTargetPos( blockx, blocky);
                    batch->draw(&sdi);
                }
            }
        }
    }

    sdi.textureHandle = game->getGameTextures().iconTexture;


    // render icons
    for (int y=0; y<height; y++) {
        float blocky = posy + y*blockSize;

        if (blocky>=-blockSize && blocky<batch->getTargetHeight()+blockSize/2);
        for (int x=0; x<width; x++) {
            int lind = y*width+x;
            if ((levelData[lind]&1) == 0)
            {
                float blockx = posx + x*blockSize;
                if (blockx>=-blockSize && blockx<batch->getTargetWidth()+blockSize/2) {
                    sdi.setTargetPos( blockx, blocky);

                    if (mineMarkAnimationState[lind]>0.01f) {

                        if (gameOver) {
                                // mines
                            if ((levelData[lind]&2)!=0) {
                                tily = 0;
                                tilx = 2;
                                sdi.setSourceRect( tilx/4.0f, tily/2.0f, 1.0f/4.0f, 1.0f/2.0f);
                                sdi.setScale( blockSize * (3.0f - mineMarkAnimationState[lind]*2.0f));
                                sdi.a = mineMarkAnimationState[lind];
                                sdi.angle = 1.0f - mineMarkAnimationState[lind];
                                    // flash it if there is no mark
                                if (mineMarkAnimationState[lind]<0.9f || (levelData[lind]&4)!=0  || flash) batch->draw(&sdi);
                            } else {


                            }
                        }



                        // draw the minefieldmark
                        if ((levelData[lind]&4)!=0) {
                            tily = 0;
                            tilx = 1;
                            sdi.setSourceRect( tilx/4.0f, tily/2.0f, 1.0f/4.0f, 1.0f/2.0f);
                            sdi.setScale( blockSize * (4.9f - mineMarkAnimationState[lind]*3.8f));
                            sdi.a = mineMarkAnimationState[lind];
                            sdi.angle = 1.0f - mineMarkAnimationState[lind];

                            if ((((levelData[lind]&2)!=0) || flash) || (gameOver==false))
                                batch->draw(&sdi);
                        }


                    }
                }
            }
        }
    }



    char num[4];
        // render numbers.
    for (int y=0; y<height; y++) {
        float blocky = posy + y*(float)blockSize;

        if (blocky>=-blockSize && blocky<batch->getTargetHeight()+blockSize/2);
        for (int x=0; x<width; x++) {
            int lind = y*width+x;
            if ((levelData[lind]&1)==1)     // already opened
            {
                float blockx = posx + x*(float)blockSize;
                if (blockx>=-blockSize && blockx<batch->getTargetWidth()+blockSize/2) {
                    if ((levelData[lind]>>4)>0) {
                        sprintf( num, "%d", levelData[lind] >> 4);
                        game->writeText(batch, num, blockx, blocky, blockSize*3/5, &number_colors[ (levelData[lind]>>4)*4 ] );
                    }
                }
            }
        }
    }
}

